package com.example.transaction.service.impl;

import com.example.transaction.model.Account;
import com.example.transaction.model.TransactionRecord;
import com.example.transaction.repository.AccountRepository;
import com.example.transaction.service.AccountService;
import com.example.transaction.service.TransactionRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;

@Service
@RequiredArgsConstructor
@Slf4j
public class AccountServiceImpl implements AccountService {

    private final AccountRepository accountRepository;
    private final TransactionRecordService transactionRecordService;

    @Override
    @Transactional
    public Account createAccount(String accountNumber, String ownerName, BigDecimal initialBalance) {
        log.info("Creating account: {}, owner: {}, initial balance: {}", accountNumber, ownerName, initialBalance);
        Account account = new Account(accountNumber, ownerName, initialBalance);
        return accountRepository.save(account);
    }

    @Override
    @Transactional(readOnly = true)
    public Account getAccount(String accountNumber) {
        log.info("Getting account: {}", accountNumber);
        return accountRepository.findByAccountNumber(accountNumber)
                .orElseThrow(() -> new RuntimeException("Account not found: " + accountNumber));
    }

    @Override
    @Transactional(readOnly = true)
    public List<Account> getAllAccounts() {
        log.info("Getting all accounts");
        return accountRepository.findAll();
    }

    @Override
    @Transactional
    public Account deposit(String accountNumber, BigDecimal amount) {
        log.info("Depositing {} to account: {}", amount, accountNumber);
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Deposit amount must be positive");
        }

        Account account = accountRepository.findByAccountNumberWithLock(accountNumber)
                .orElseThrow(() -> new RuntimeException("Account not found: " + accountNumber));

        account.setBalance(account.getBalance().add(amount));
        return accountRepository.save(account);
    }

    @Override
    @Transactional
    public Account withdraw(String accountNumber, BigDecimal amount) {
        log.info("Withdrawing {} from account: {}", amount, accountNumber);
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Withdrawal amount must be positive");
        }

        Account account = accountRepository.findByAccountNumberWithLock(accountNumber)
                .orElseThrow(() -> new RuntimeException("Account not found: " + accountNumber));

        if (account.getBalance().compareTo(amount) < 0) {
            throw new RuntimeException("Insufficient funds in account: " + accountNumber);
        }

        account.setBalance(account.getBalance().subtract(amount));
        return accountRepository.save(account);
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    public void transfer(String fromAccountNumber, String toAccountNumber, BigDecimal amount) {
        log.info("Transferring {} from account: {} to account: {}", amount, fromAccountNumber, toAccountNumber);
        if (fromAccountNumber.equals(toAccountNumber)) {
            throw new IllegalArgumentException("Cannot transfer to the same account");
        }

        // 创建交易记录
        TransactionRecord transactionRecord = transactionRecordService.createTransactionRecord(
                fromAccountNumber, toAccountNumber, amount);

        try {
            // 从源账户扣款
            withdraw(fromAccountNumber, amount);
            
            // 模拟可能的异常情况 - 取消注释以测试事务回滚
            // if (amount.compareTo(new BigDecimal("500")) > 0) {
            //     throw new RuntimeException("Simulated error for large transfers");
            // }
            
            // 向目标账户存款
            deposit(toAccountNumber, amount);
            
            // 更新交易状态为完成
            transactionRecordService.updateTransactionStatus(
                    transactionRecord.getTransactionId(), TransactionRecord.TransactionStatus.COMPLETED);
            
            log.info("Transfer completed successfully");
        } catch (Exception e) {
            // 更新交易状态为失败
            transactionRecordService.updateTransactionStatus(
                    transactionRecord.getTransactionId(), TransactionRecord.TransactionStatus.FAILED);
            
            log.error("Transfer failed: {}", e.getMessage());
            throw e; // 重新抛出异常，触发事务回滚
        }
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void batchTransfer(List<TransferRequest> transfers) {
        log.info("Starting batch transfer with {} transfers", transfers.size());
        
        for (TransferRequest request : transfers) {
            try {
                // 这里使用REQUIRES_NEW来演示嵌套事务
                // 每个子事务都是独立的，一个子事务的失败不会影响其他子事务
                transferWithNewTransaction(request.getFromAccountNumber(), 
                                          request.getToAccountNumber(), 
                                          request.getAmount());
            } catch (Exception e) {
                log.error("Error in batch transfer: {}", e.getMessage());
                // 这里可以选择继续处理其他转账，或者抛出异常中断整个批处理
                // throw e; // 取消注释以测试整个批处理的回滚
            }
        }
        
        log.info("Batch transfer completed");
    }
    
    // 使用REQUIRES_NEW传播行为的内部方法，创建新的事务
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void transferWithNewTransaction(String fromAccountNumber, String toAccountNumber, BigDecimal amount) {
        log.info("Executing transfer in new transaction: {} from {} to {}", 
                amount, fromAccountNumber, toAccountNumber);
        transfer(fromAccountNumber, toAccountNumber, amount);
    }
}